package com.onpositive.simple.persistance;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.BitSet;
import java.util.EmptyStackException;
import java.util.Stack;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.IntBuffer;

public class FSStringStorage implements IStringStorage {

	final static int intSize = 4  ;
	final static int charSize = 2 ;
	RandomAccessFile metricParams, container;
	ByteBuffer byteBuffer ;
	IntBuffer intBuffer ;//= byteBuffer.asIntBuffer() ;
	CharBuffer charBuffer ;//= byteBuffer.asCharBuffer() ;
	StringBuilder stringBuilder ;
	
	protected BitSet deletedIdFlag ;
	protected Stack<Integer> vacantID ;
	
	protected BitSet emptyCharFlag ;
	protected int stringsCount, paramsOffset, containerLength ; 
	
	
	public FSStringStorage( String path )
	{
		String metricParamsPath = path + "smparams.dat" ;
		String containerPath = path + "scontainer.dat" ;
		
		try {
			metricParams = new RandomAccessFile( metricParamsPath, "rws" ) ;
			container = new RandomAccessFile(containerPath, "rws") ;
			container.setLength(0);
			metricParams.setLength(0);
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
		
		emptyCharFlag = new BitSet() ;
		deletedIdFlag = new BitSet() ;
		vacantID = new Stack<Integer>() ;
		stringsCount = 0 ;
		containerLength = 0 ;
		paramsOffset = 2*intSize ;
		
		byteBuffer = ByteBuffer.allocate( 4*intSize ) ;
		intBuffer = byteBuffer.asIntBuffer() ;
		charBuffer = byteBuffer.asCharBuffer() ;
		
		intBuffer.clear() ;
		intBuffer.put(stringsCount);
		byte content[] = new byte[intSize] ;
		byteBuffer.get(content) ;
		
		try {
			metricParams.seek(0) ;
			metricParams.write( content, 0, intSize ) ;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	@Override
	public int put(String s) {
		
		int id = getVacantId() ;
		deletedIdFlag.set( id, false ) ;
		
		int params[] = new int[2] ;
		
		int stringSizeInBytes = charSize * s.length() ;
		params[0] = containerLength ;
		params[1] = s.length() ;
		
		putParams( id, params, id == stringsCount ) ;

		stringsCount++ ;
		containerLength += stringSizeInBytes ;
		
		byteBuffer.clear() ;

		if( byteBuffer.capacity() < stringSizeInBytes ){
			byteBuffer = ByteBuffer.allocate( stringSizeInBytes ) ;
			intBuffer = byteBuffer.asIntBuffer() ;
			charBuffer = byteBuffer.asCharBuffer() ;
		}

		charBuffer.clear();
		charBuffer.put(s) ;
		
		try {
			if( container.length() < params[0] + stringSizeInBytes )
				reallocateContainer( params[0] + stringSizeInBytes ) ;
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		
		byte content[] = new byte[ stringSizeInBytes ] ;
		byteBuffer.get(content) ;
		
		try {
			container.seek(params[0]);
			container.write(content, 0, stringSizeInBytes ) ;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return id;
	}
	

	void putParams( int id , int params[], boolean containerLengthChanged )
	{
		
		byteBuffer.clear() ;
		intBuffer.clear() ;
		intBuffer.put(params) ;
		if( containerLengthChanged ){

			intBuffer.put(stringsCount+1) ;
			intBuffer.put(containerLength + charSize*params[1]) ;
		}
		byte content[] = new byte[4*intSize] ;
		
		int position = paramsOffset + 2*id*intSize ;
		int requiredLength = position + 2*intSize ;
		
		try {
			if( metricParams.length() < requiredLength )
				reallocateMetricParams( requiredLength ) ;
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		byteBuffer.get(content, 0, 4*intSize) ;
		
		try {
			metricParams.seek( position ) ;
			metricParams.write(content, 0 , 2*intSize) ;
		} catch (IOException e) {
			e.printStackTrace();
		}
		if( containerLengthChanged ){

			try {
				metricParams.seek( 0 ) ;
				metricParams.write(content, 2*intSize, 2*intSize) ;
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	

	int[] getParams( int id )
	{
		byte content[] = new byte[2*intSize] ;
		try {
			metricParams.seek( paramsOffset + 2*intSize*id ) ;
			metricParams.read( content , 0, 2*intSize ) ;
		} catch (IOException e) {
			e.printStackTrace();
		}
		int params[] = new int[2];
		byteBuffer.clear();
		intBuffer.clear() ;
		byteBuffer.put(content) ;
		intBuffer.get(params) ;
		return params ;
	}
	
	@Override
	public String get(int id) {
		
		int params[] = getParams(id) ;// params == [offset,length] ;
		int  stringLengthInBytes = charSize * params[1] ;
		byte content[] = new byte[ stringLengthInBytes ] ;
		
		try {
			container.seek( params[0] ) ;
			container.read( content , 0, stringLengthInBytes ) ;
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		if( byteBuffer.capacity() < stringLengthInBytes ){
			byteBuffer = ByteBuffer.allocate(stringLengthInBytes) ;
			intBuffer = byteBuffer.asIntBuffer() ;
			charBuffer = byteBuffer.asCharBuffer() ;
		}
		
		byteBuffer.clear() ;
		charBuffer.clear() ;
		byteBuffer.put( content ) ;
		char charArray[] = new char[ params[1]] ;
		charBuffer.get( charArray ) ;
		
		String s = new String( charArray ); 
		return s ;
	}

	@Override
	public void delete(int id) {
		
	}
	protected int getVacantId()
	{
		Integer id ;
		try{
			id = vacantID.pop() ;
		}
		catch(EmptyStackException e){
			id = null ; 
		}		
		
		if( id == null ){
			
//			byte content[] = new byte[intSize] ;
//			try {
//				metricParams.read(content, 0, intSize) ;
//			} catch (IOException e) {
//				e.printStackTrace();
//			}
//			byteBuffer.clear() ;
//			byteBuffer.put(content) ;
//			id = intBuffer.get(0) ;
			id = stringsCount ;
		}
		return id ;		
	}
	
	protected void clearGarbage()
	{
		
	}
	
	private void reallocateMetricParams(int requiredLength) {
		
		long length ;
		try {
			length = (int) metricParams.length() ;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			length = 1 ;
		}
		
		for (  ; length < requiredLength ; )
			length = (long)(length * 1.4 + 1) ;
		
		try {
			metricParams.setLength(length) ;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
		
	}
	private void reallocateContainer(int requiredLength) {


		long length ;
		try {
			length = (int) container.length() ;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			length = 1 ;
		}
		
		for (  ; length < requiredLength ; )
			length = (long)(length * 1.4 + 1) ;
		
		try {
			container.setLength(length) ;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
		
	}

}
